
#include "exception.hpp"
#include "sock-inet-udp.hpp"

#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>

////////////////////////////////////////////////////////////////////////////////

sock_inet_udp::packet::packet(uint8_t* buffer, size_t len)
    : buffer(buffer), const_buffer(0), len(len), addr(0, 0)
{
}

sock_inet_udp::packet::packet(const uint8_t* buffer, size_t len, const address& addr)
    : buffer(0), const_buffer(buffer), len(len), addr(addr)
{
}

sock_inet_udp::packet::packet(const packet& p)
    : buffer(p.buffer), const_buffer(p.const_buffer), len(p.len), addr(p.addr)
{
    if(!const_buffer)
    {
        const_buffer = buffer;
    }
}

const uint8_t* sock_inet_udp::packet::get_buffer() const
{
    return const_buffer;
}

uint8_t* sock_inet_udp::packet::get_buffer()
{
    return buffer;
}

size_t sock_inet_udp::packet::get_len() const
{
    return len;
}

const sock_inet::address& sock_inet_udp::packet::get_address() const
{
    return addr;
}

sock_inet::address& sock_inet_udp::packet::get_address()
{
    return addr;
}

////////////////////////////////////////////////////////////////////////////////

sock_inet_udp::sock_inet_udp()
    : sock_inet(SOCK_DGRAM, IPPROTO_UDP)
{
}

sock_inet_udp& sock_inet_udp::operator>>(const sock_inet::timeout& t)
{
    if(!wait(t))
    {
        throw exception_timeout();
    }

    return *this;
}

sock_inet_udp& sock_inet_udp::operator>>(sock_inet_udp::packet& p)
{
    recvfrom(p);
    return *this;
}

sock_inet_udp& sock_inet_udp::operator<<(const sock_inet_udp::packet& p)
{
    sendto(p);
    return *this;
}

void sock_inet_udp::recvfrom(sock_inet_udp::packet& p)
{
    socklen_t from_len = sizeof(sockaddr);

    p.len = ::recvfrom(sfd, (void*)p.get_buffer(), p.get_len(), 0, (sockaddr*)&p.get_address().get_sockaddr(), &from_len); // TODO: why (sockaddr*) is needed?

    if(p.len < 0)
    {
        throw std::runtime_error("recvfrom"); // TODO: specific exception
    }
}

void sock_inet_udp::sendto(const sock_inet_udp::packet& p)
{
    if((size_t)::sendto(sfd, (void*)p.get_buffer(), p.get_len(), 0, &p.get_address().get_sockaddr(), (int)sizeof(sockaddr)) != p.len)
    {
        throw std::runtime_error("sendto"); // TODO: specific exception
    }
}

sock_inet_udp sock_inet_udp::bind(const sock_inet::address& a)
{
    return sock_inet_udp(a);
}

sock_inet_udp::sock_inet_udp(const sock_inet::address& a)
    : sock_inet(SOCK_DGRAM, IPPROTO_UDP)
{
    sock_inet::bind(a);
}

////////////////////////////////////////////////////////////////////////////////
